
怎麼是梗圖開場 XD (來源:網路)
前幾天我們介紹了 Vue 中響應式的基礎 ref 以及 Proxy 的機制,今天直接來看另一種用法 reactive!
由於篇幅較長,先列出摘要:
- 定義
- 語法
reactive 和 ref 不同之處- reactive 特點
Proxy 物件並不等於原始物件
只有 Proxy 物件具響應性,原始物件不具備
Proxy 物件怎麼確保響應性?- reactive 的局限性
不能夠綁定原始值
不能隨意更動綁定的原始物件
解構後會丟失響應性- 官方建議
我們開始囉!![]()
官方文件:還有另一種聲明響應式狀態的方式,即使用 reactive() API。與將內部值包裝在特殊對象中的 ref 不同,reactive() 將使對象本身具有響應性
響應式對象是 JavaScript 代理,其行為就和普通對象一樣。不同的是,Vue 能夠攔截對響應式對象所有屬性的訪問和修改,以便進行依賴追蹤和觸發更新。
reactive();
括號內可定義「物件值」,也就是你要請 reactive 追蹤的參數。
我們先悄悄說一下 reactive 這個方式的綁定限制,之後會以範例帶大家來看看:
官方文件:它只能用於對象類型 (對象、數組和如 Map、Set 這樣的集合類型)。它不能持有如 string、number 或 boolean 這樣的原始類型。
不過,之前有提到 ref 也能夠綁定物件,那為什麼還需要 reactive 呢?
reactive 是直接透過 Proxy 物件的概念實現的,會讓物件本身具有響應性。ref 則是將自己包裝成 RefImfp 物件去追蹤 _value 屬性的變更。我們可以分別用它倆包一個物件來看看區別!
<script setup>
import { reactive } from "vue";
import { ref } from "vue";
const obj = { today: "今天是鐵人賽第 14 天,你還活著嗎" };
const reactiveObj = reactive(obj);
const refObj = ref(obj);
console.log(reactiveObj);
console.log(refObj);
</script>
<template>
<h3>reactive example: {{ reactiveObj.today }}</h3>
<h3>ref example: {{ refObj.today }}</h3>
</template>
以上做了什麼:
obj 物件,其中包含屬性 today,並用 reactive 和 ref 兩種方式進行響應性綁定。reactiveObj 是通過 reactive 將 obj 轉換為一個 Proxy 物件,會使整個物件都具有響應性。refObj 是通過 ref 將 obj 包裝起來的,是一個 RefImpl 物件。瀏覽器上可以看到:
reactiveObj:它是一個 Proxy 物件,內部有 today 屬性。我們複習一下昨天講到的 Proxy 概念:它是一個代理物件,內部會有攔截機制(get、set 等等方法)。
因此我們在模板使用 <h3>reactive example: {{ reactiveObj.today }}</h3> 時,會觸發內部 Proxy 的 get 機制,因此可取到 today 的值。
refObj:是一個 RefImpl 物件,用 _value 值去追蹤內部的值,而 _value 內有一個 Proxy 物件。我們複習一下 ref 在模板中解包的 概念:
所以在這邊我們使用 <h3>ref example: {{ refObj.today }}</h3> 時:
refObj 這個 RefImpl 物件會自動解包(不用透過 _value 拿到 obj)obj 是一個物件,所以我們透過 .today 再取得值。
Proxy 物件並不等於原始物件Proxy 物件具響應性,原始物件不具備我們可以從中觀察:
<script setup>
import { reactive } from "vue";
const obj = { today: "今天是鐵人賽第 14 天,你還活著嗎" };
const reactiveObj = reactive(obj);
console.log(`Proxy 物件是否等於原始物件:`, obj === reactiveObj);
</script>

答案是 false。
官方文件:只有代理對象是響應式的,更改原始對象不會觸發更新。因此,使用 Vue 的響應式系統的最佳實踐是僅使用你聲明對象的代理版本。
我們可以這樣試試看去改原始物件、代理物件:
<script setup>
import { reactive } from "vue";
const obj = { answer: "" };
const reactiveObj = reactive(obj);
// 修改原始物件
function changeOriginal() {
obj.answer = "原始物件鼠掉了";
console.log(obj);
}
// 修改代理物件
function changeReactive() {
reactiveObj.answer = "代理物件鼠掉了";
console.log(reactiveObj);
}
</script>
<template>
<div class="container">
<h1>今天是鐵人賽第 14 天</h1>
<p>點擊下面的按鈕來看看原始物件和代理物件的不同反應。</p>
<div>
<h3>· 原始物件 你還活著嗎? {{ obj.answer }}</h3>
<button @click="changeOriginal">原始物件要回答</button>
</div>
<div>
<h3>· 代理物件 你還活著嗎? {{ reactiveObj.answer }}</h3>
<button @click="changeReactive">代理物件要回答</button>
</div>
</div>
</template>
我們用 changeOriginal() 和 changeReactive() 兩個 function 分別去對 obj 、reactiveObj 的值做修改並將目前原始物件印出。
changeOriginal 不會觸發 UI 更新changeReactive 使用 reactiveObj 響應式物件修改的值會觸發 reactive(obj) 進而觸發 UI 更新。瀏覽器上可看見:
兩個按鈕點擊後 obj 物件中的值是有被更改的,唯獨差異是:直接用「原始物件」方式改的,「不會觸發」 UI 更新。
這邊也要提到為什麼用 reactiveObj.answer = "代理物件鼠掉了"; 這個方法改值,{{ obj.answer }} 的 UI 也會更新?因為原始物件和代理物件是指向同一個記憶體的 因為 Proxy 物件是對原始物件的代理,當代理物件修改屬性時,這些更改會「直接反映到原始物件」上,所以修改代理物件的屬性會同步影響原始物件的屬性。
鼠掉了沒有關係,還是鼠出了電光一閃
(import 橘子の DOM Event 系列文 - 元素不可思議事件簿!很有趣 XD
官方文件:為保證訪問代理的一致性,對同一個原始對象調用 reactive() 會總是返回同樣的代理對象,而對一個已存在的代理對象調用 reactive() 會返回其本身
翻譯蒟蒻:所以說只要使用了 reactive() 來綁定物件,它的原始物件和代理物件都會返回同一個代理物件。
我們可以看看這個例子:
<script setup>
import { reactive } from "vue";
const rawObj = {};
const proxyObj = reactive(rawObj);
const proxyObj2 = reactive(proxyObj);
console.log(
`reactive(rawObj) 是否等於 proxyObj:`,
reactive(rawObj) === proxyObj
);
console.log(
`reactive(proxyObj) 是否等於 proxyObj:`,
reactive(proxyObj) === proxyObj
);
console.log(
`reactive(proxyObj2) 是否等於 proxyObj:`,
reactive(proxyObj2) === proxyObj
);
</script>
reactive(rawObj) === proxyObj:綁定了原始物件 rawObj,return 的是代理物件 proxyObj。reactive(proxyObj) === proxyObj:綁定了第一次定義的代理物件 proxyObj,return 的是它自己。reactive(proxyObj2) === proxyObj:綁定了第一次定義的代理物件的代理物件 proxyObj2,return 的還是第一次定義的代理物件 proxyObj。
無論重複綁定多少次,還是會得到「同一個」代理物件:因此確保了響應式數據被正確控管。
當我們嘗試著綁定「數字」或「字串」時:
<script setup>
import { reactive } from "vue";
const reactiveObj = reactive(123);
const reactiveObj2 = reactive("abc");
console.log(reactiveObj);
console.log(reactiveObj2);
</script>
<template>
<h3>{{ reactiveObj }}</h3>
<h3>{{ reactiveObj2 }}</h3>
</template>
瀏覽器上會看到:
專案看起來並不會報錯,定義的值會正常渲染耶,但是出現了一些警告:

那我們修改值呢?
<script setup>
import { reactive } from "vue";
const reactiveObj = reactive(123);
const reactiveObj2 = reactive("abc");
console.log(reactiveObj);
console.log(reactiveObj + 1);
console.log(reactiveObj2);
console.log(reactiveObj2 + "edf");
</script>
<template>
<h3>{{ reactiveObj }}</h3>
<h3>{{ reactiveObj2 }}</h3>
</template>
<template> 中的文本插值並沒有更新到最新的值!

官方文件:由於 Vue 的響應式跟蹤是通過屬性訪問實現的,因此我們必須始終保持對響應式對象的相同引用。這意味著我們不能輕易地“替換”響應式對象,因為這樣的話與第一個引用的響應性連接將丟失:
let state = reactive({ count: 0 }) // 上面的 ({ count: 0 }) 引用將不再被追蹤 // (響應性連接已丟失!) state = reactive({ count: 1 })
我們來寫寫範例:
<script setup>
import { reactive } from "vue";
let state = reactive({ count: 0 });
function changeState() {
state = reactive({ count: 1 }); // 替換物件,這樣會丟失響應性
}
function changeValue() {
state.count++; // 修改屬性
}
</script>
<template>
<div>
<h3>替換物件範例</h3>
<p>目前值:{{ state.count }}</p>
<button @click="changeState">替換 state</button>
<h3>修改屬性範例</h3>
<p>目前值:{{ state.count }}</p>
<button @click="changeValue">增加計數</button>
</div>
</template>
reactive() 追蹤的物件 state。changeState 函式:{ count: 0 } 改為 { count: 1 }。changeValue 函式:state.count++; 增加響應式物件屬性的值。
可以看到:
count 的值會正常增加,介面會隨之更新。state」的按鈕後,state 物件被全新的物件 { count: 1 } 替換,雖然可看到 count 的值已經被設為 1,介面卻不會更新,因為替換後導致 state 失去原本的響應性。<script setup>
import { reactive } from "vue";
const reactiveObj = reactive({ count: 0 });
let { count } = reactiveObj;
count++;
console.log(`reactiveObj`, reactiveObj);
console.log(`count`, count);
</script>
<template>
<h3>已經斷開響應性連接的:{{ reactiveObj.count }}</h3>
<h3>被解構的變數:{{ count }}</h3>
</template>
來看看這段程式碼做了什麼:
const reactiveObj = reactive({ count: 0 });:使用 reactive() 將 { count: 0 } 物件用響應式綁定為 reactiveObj 變數。let { count } = reactiveObj;:解構代理物件中的屬性為 count 變數。count 變數++。reactiveObj 與解構後的變數 count。<template> 中看兩者是否具備響應性。瀏覽器上會看到:
右邊:
reactiveObj 解構後,其內的 count 屬性 _value 還是 0,並非 1count 值為 ++ 過的 1。左邊我們使用:
<template>
<h3>已經斷開響應性連接的:{{ reactiveObj.count }}</h3>
<h3>被解構的變數:{{ count }}</h3>
</template>
觀察兩者是否具備響應性,得出 reactiveObj 被解構後其屬性值 count,不具備響應性,所以還是 0。
而 count 則為執行 count++ 後的值 1。
官方文件:由於這些限制,我們建議使用 ref() 作為聲明響應式狀態的主要 API。
不過作者 尤雨溪 有在 這個影片 中談過:每個人使用的時候,多少帶點主觀成份,但只要你覺得它對是更適合你的想法,就不會有太大問題。主要還是看個人偏好和團隊共識。
覺得 reactive 它的細節和要注意的地方更多(腦袋爆炸中)
而兩者的區別和更進階的應用,應該之後實作的時候就會比較有感(應該吧 嗎)![]()
https://github.com/Jamixcs/2024iThome-jamixcs/tree/main/src/components/day14